#include "statistics_view.hpp"

#include "app/app_base.hpp"
#include "event/events.hpp"
#include "imgui/imgui.h"
#include "imgui/imgui_stdlib.h"
#include "imgui/IconsFontAwesome4.h"
#include "utils/date_utils.hpp"
#include "utils/path_utils.hpp"
#include "json/json.hpp"
#include "fmt/format.h"
#include "statistics_tab.hpp"
#include "gui/controls/calendar.hpp"

#include <array>
#include <fstream>
#include <variant>
#include <regex>
#include <unordered_map>

struct StatisticsData
{
    enum StatisticsType
    {
        eBegin = 0,

        eLastWeek = eBegin,
        eLastMonth,
        eLastQuarter,
        eLastYear,
        eThisWeek,
        eThisMonth,
        eThisQuarter,
        eThisYear,

        eCount,
    };

    struct StatisticItemSetting
    {
        bool is_builtin;
        std::variant<StatisticsType, std::string> value;

        friend bool operator==(const StatisticItemSetting& lhs, const StatisticItemSetting& rhs)
        {
            return lhs.is_builtin == rhs.is_builtin
                && lhs.value == rhs.value;
        }

        friend bool operator!=(const StatisticItemSetting& lhs, const StatisticItemSetting& rhs)
        {
            return !(lhs == rhs);
        }
    };

    StatisticsData();
    ~StatisticsData();

    void LoadConfig();
    void SaveConfig();

    void AddPreDefine(int type);
    void AddUserRange(const std::string& start, const std::string& end);
    void RemoveStatistics(const StatisticItemSetting& setting);
    void ShowCustomStatistics(const std::string &date_begin, const std::string &date_end);

    static void GetDateStrings(int what, std::string& start, std::string& end);

    Listener event_bus;
    std::list<StatisticItemSetting> settings;
    std::unordered_map<void*, std::unique_ptr<StatisticsTab>> tabs;
    std::unique_ptr<StatisticsTab> custom_statistics_tab;
};

using namespace ImGui;

StatisticsView::StatisticsView() : d{ new StatisticsData }
{
}

StatisticsView::~StatisticsView()
{
    delete d;
}

unsigned StatisticsView::Flags() const
{
    return eTab | eMenu;
}

void StatisticsView::RenderContent(ComponentFlag flag)
{
    if (flag == eTab)
        Tab();
    else if (flag == eMenu)
        Menu();
}

const char* StatisticsView::GetName(ComponentFlag flag)
{
    if (flag == eTab)
        return TAB_NAME_OF(AppEvent::SelectTabEvent::eStatistics);
    return nullptr;
}

static std::array pre_defines{
    StatisticsData::eLastWeek,
    StatisticsData::eLastMonth,
    StatisticsData::eLastQuarter,
    StatisticsData::eLastYear,
    StatisticsData::eThisWeek,
    StatisticsData::eThisMonth,
    StatisticsData::eThisQuarter,
    StatisticsData::eThisYear
};
static const char* pre_define_texts[]
{
    ICON_FA_CALENDAR " 上周",
    ICON_FA_CALENDAR " 上月",
    ICON_FA_CALENDAR " 上季度",
    ICON_FA_CALENDAR " 上年",
    ICON_FA_CALENDAR " 本周",
    ICON_FA_CALENDAR " 本月",
    ICON_FA_CALENDAR " 本季度",
    ICON_FA_CALENDAR " 本年",
};

static_assert (size(pre_defines) == std::size(pre_define_texts));

void StatisticsView::Menu()
{
    if (BeginMenu(ICON_FA_AREA_CHART " 统计"))
    {
        AlignTextToFramePadding();
        for (auto const& x : d->settings)
        {
            if (x.is_builtin)
            {
                const int id = std::get<StatisticsData::StatisticsType>(x.value);
                Text("%s", pre_define_texts[id]);
            }
            else
            {
                auto ranges = std::get<std::string>(x.value);
                ranges = ICON_FA_CALENDAR " " + ranges;
                Text("%s", ranges.data());
            }
            PushID(&x);
            SameLine(GetFontSize() * 15);
            // SetCursorPosX();
            if (Button(ICON_FA_TIMES_CIRCLE_O))
            {
                d->RemoveStatistics(x);
                CloseCurrentPopup();
            }
            PopID();
        }

        if (BeginMenu(ICON_FA_PLUS " 添加统计"))
        {
            for (auto const& x : pre_defines)
            {
                if (MenuItem(pre_define_texts[x]))
                {
                    d->AddPreDefine(x);
                }
            }

            if (BeginMenu(ICON_FA_PENCIL_SQUARE_O " 自定义"))
            {
                AlignTextToFramePadding();

                static std::string date_start = Date::Current();
                static DateEditor  ds_editor("##StartDate");
                if (ds_editor.RenderAsPopup(false)) date_start = ds_editor.GetSelectedDate();

                static std::string date_end = Date::Current();
                static DateEditor  de_editor("##EndDate");
                if (de_editor.RenderAsPopup(false)) date_end = de_editor.GetSelectedDate();
                
                BulletText("开始日期"); SameLine(); if (Button("编辑##Start")) ds_editor.Show();
                InputTextWithHint("##StartDate", "如：2020-01-01", &date_start);

                AlignTextToFramePadding();
                BulletText("结束日期"); SameLine(); if (Button("编辑##End")) de_editor.Show();
                InputTextWithHint("##EndDate", "如：2020-12-31", &date_end);

                static std::regex date_reg{ R"regex(^\d{4}-\d{2}-\d{2})regex" };
                BeginDisabled(!std::regex_match(date_start, date_reg) || !std::regex_match(date_end, date_reg));
                if (Button(ICON_FA_CHECK_CIRCLE_O " 确定"))
                {
                    d->AddUserRange(date_start, date_end);
                    date_start = date_end = "";
                    CloseCurrentPopup();
                }
                EndDisabled();
                ImGui::EndMenu();
            }
            ImGui::EndMenu();
        }

        ImGui::EndMenu();
    }
}

void StatisticsView::Tab()
{
    if (BeginTabBar("##统计数据页", ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_NoCloseWithMiddleMouseButton))
    {
        for (auto& x : d->settings)
        {
            d->tabs.at(&x)->Render();
        }

        // 添加实时自定义统计功能
        d->custom_statistics_tab->Render();

        EndTabBar();
    }
}

StatisticsData::StatisticsData(): event_bus(App->Bus())
{
    event_bus.listen<AppEvent::QuickShowStatisticsEvent>([this](const AppEvent::QuickShowStatisticsEvent &e){
        std::string start,end;
        DateComplition(e.date_prefix, start, end);
        ShowCustomStatistics(start, end);
    });
    event_bus.listen<AppEvent::ShowStatisticsByRangeEvent>([this](const auto &e){
        ShowCustomStatistics(e.start, e.end);
    });
    LoadConfig();
}

StatisticsData::~StatisticsData()
{
    event_bus.unlistenAll();
    SaveConfig();
}

void StatisticsData::LoadConfig()
{
    auto cfg_path = "statistics.json"_DP;
    std::ifstream ifs(cfg_path, std::ios::in | std::ios::binary);
    if (ifs.is_open())
    {
        auto cfg_json = nlohmann::json::parse(ifs);
        if (cfg_json.is_array())
        {
            for (auto& x : cfg_json)
            {
                if (x.value<bool>("is_builtin", false))
                {
                    const auto type = x.value<int>("value", -1);
                    if (type >= eBegin && type < eCount)
                    {
                        AddPreDefine(type);
                    }
                }
                else
                {
                    const auto date_range = x.value<std::string>("value", "");
                    static std::regex reg{ R"regex(^\d{4}-\d{2}-\d{2}:\d{4}-\d{2}-\d{2}$)regex" };
                    if (std::regex_match(date_range, reg))
                    {
                        AddUserRange(date_range.substr(0, 10), date_range.substr(11));
                    }
                }
            }
        }
    }

    // always add custom statistics tab
    custom_statistics_tab = std::make_unique<StatisticsTab>(ICON_FA_PENCIL_SQUARE_O " 自定义统计");
}

void StatisticsData::SaveConfig()
{
    auto cfg_path = "statistics.json"_DP;
    std::ofstream ofs(cfg_path, std::ios::out | std::ios::binary | std::ios::trunc);
    if (ofs.is_open())
    {
        auto arr = nlohmann::json::array();

        for (const auto& x : settings)
        {
            if (x.is_builtin)
            {
                arr.emplace_back(
                    nlohmann::json{
                        {"is_builtin", true},
                        {"value", static_cast<int>(std::get<StatisticsType>(x.value))}
                    }
                );
            }
            else
            {
                arr.emplace_back(
                    nlohmann::json{
                        {"is_builtin", false},
                        {"value", std::get<std::string>(x.value)}
                    }
                );
            }
        }
        ofs << arr.dump();
        ofs.close();
    }
}

void StatisticsData::AddPreDefine(int type)
{
    assert(type >= eBegin && type <= eCount);

    if (const auto v = std::ranges::find_if(settings, [type](auto& x)
        {
            return x.is_builtin && std::get<StatisticsType>(x.value) == type;
        }); v == end(settings))
    {
        auto& t = settings.emplace_back(true, static_cast<StatisticsType>(type));
        tabs.emplace(&t, std::make_unique<StatisticsTab>(pre_define_texts[type], [type](std::string& start, std::string& end) {
            GetDateStrings(type, start, end);
            }));
        SaveConfig();
    }
}

void StatisticsData::AddUserRange(const std::string& start, const std::string& end)
{
    auto const key = fmt::format("{}:{}", start, end);
    if (const auto v = std::ranges::find_if(settings, [key](auto& x)
        {
            return !x.is_builtin && std::get<std::string>(x.value) == key;
        }); v == std::end(settings))
    {
        auto& t = settings.emplace_back(false, key);
        tabs.emplace(&t, std::make_unique<StatisticsTab>(key, [start, end](std::string& ds, std::string& de) {
            ds = start;
            de = end;
            }));
        SaveConfig();
    }
}

void StatisticsData::RemoveStatistics(const StatisticItemSetting& setting)
{
    App->Post([this, &setting]
        {
            const auto it = std::ranges::find_if(settings, [&setting](const auto& x)
                {
                    return x == setting;
                });
            if (it != end(settings))
            {
                tabs.erase(&(*it));
                settings.erase(it);
            }
            SaveConfig();
        });
}

#include <fmt/chrono.h>
#include <ctime>

void StatisticsData::GetDateStrings(int what, std::string& start, std::string& end)
{
    assert(what >= eBegin && what <= eCount);

    static constexpr auto days = [](int day)-> time_t { return 60ll * 24 * 60 * day; };

    // get current date
    const time_t now = time(nullptr);
    struct tm tm {};
#if _MSC_VER
    localtime_s(&tm, &now);
#else
    localtime_r(&now, &tm);
#endif

    static constexpr int year_date[][2]
    {
        {1, 31}, // 1月
        {1, 28}, // 2月
        {1, 31}, // 3月
        {1, 30}, // 4月
        {1, 31}, // 5月
        {1, 30}, // 6月
        {1, 31}, // 7月
        {1, 31}, // 8月
        {1, 30}, // 9月
        {1, 31}, // 10月
        {1, 30}, // 11月
        {1, 31}, // 12月
    };
    static_assert(std::size(year_date) == 12);

    static constexpr auto is_leap_year = [](int year)
    {
        return year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
    };

    static const char* quarter_date[][2]
    {
        {"01-01", "03-31"}, // 1-3月
        {"04-01", "06-30"}, // 4-6月
        {"07-01", "09-30"}, // 7-9月
        {"10-01", "12-31"}, // 10-12月
    };
    static_assert(std::size(quarter_date) == 4);

    switch (static_cast<StatisticsType>(what))
    {
    case eLastWeek:
    {
        const auto ts = now - days((tm.tm_wday + 6) % 7 + 7);
        const auto te = now - days(7 - (7 - tm.tm_wday) % 7);
        start = fmt::format("{:%Y-%m-%d}", fmt::localtime(ts));
        end = fmt::format("{:%Y-%m-%d}", fmt::localtime(te));
    }
    break;
    case eLastMonth:
    {
        const int year = (tm.tm_mon == 0 ? tm.tm_year - 1 : tm.tm_year) + 1900;
        const int month = (tm.tm_mon + 11) % 12;
        int add = 0;
        if (month == 1 && is_leap_year(year)) // 2月
            add = 1;
        start = fmt::format("{}-{:02d}-{:02d}", year, month + 1, year_date[month][0]);
        end = fmt::format("{}-{:02d}-{:02d}", year, month + 1, year_date[month][1] + add);
    }
    break;
    case eLastQuarter:
    {
        int quarter_id = tm.tm_mon / 3; // tm_mon is [0,11]
        const int year = (quarter_id == 0 ? tm.tm_year - 1 : tm.tm_year) + 1900;
        quarter_id = (quarter_id + 3) % 4;
        start = fmt::format("{}-{}", year, quarter_date[quarter_id][0]);
        end = fmt::format("{}-{}", year, quarter_date[quarter_id][1]);
    }
    break;
    case eLastYear:
    {
        start = fmt::format("{}-01-01", tm.tm_year + 1900 - 1);
        end = fmt::format("{}-12-31", tm.tm_year + 1900 - 1);
    }
    break;
    case eThisWeek:
    {
        const auto ts = now - days((tm.tm_wday + 6) % 7);
        const auto te = now + days((7 - tm.tm_wday) % 7);
        start = fmt::format("{:%Y-%m-%d}", fmt::localtime(ts));
        end = fmt::format("{:%Y-%m-%d}", fmt::localtime(te));
    }
    break;
    case eThisMonth:
    {
        const int year = tm.tm_year + 1900;
        const int month = tm.tm_mon;
        int add = 0;
        if (month == 1 && is_leap_year(year)) // 2月
            add = 1;
        start = fmt::format("{}-{:02d}-{:02d}", year, month + 1, year_date[month][0]);
        end = fmt::format("{}-{:02d}-{:02d}", year, month + 1, year_date[month][1] + add);
    }
    break;
    case eThisQuarter:
    {
        const int quarter_id = tm.tm_mon / 3; // tm_mon is [0,11]
        start = fmt::format("{}-{}", tm.tm_year + 1900, quarter_date[quarter_id][0]);
        end = fmt::format("{}-{}", tm.tm_year + 1900, quarter_date[quarter_id][1]);
    }
    break;
    case eThisYear:
    {
        start = fmt::format("{}-01-01", tm.tm_year + 1900);
        end = fmt::format("{}-12-31", tm.tm_year + 1900);
    }
    break;
    default: abort();
    }
}

void StatisticsData::ShowCustomStatistics(const std::string &date_begin, const std::string &date_end)
{
    // 先选中统计标签页
    App->Bus()->postpone(AppEvent::SelectTabEvent{
        .which = AppEvent::SelectTabEvent::eStatistics
    });

    // 再选中自定义统计
    custom_statistics_tab->SetDate(date_begin, date_end);
    custom_statistics_tab->Select();
}
